package br.edu.ufcg.splab.techniques.reduction.heuristics;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

import br.edu.ufcg.splab.generator.TestCase;
import br.edu.ufcg.splab.generator.TestSuite;
import br.edu.ufcg.splab.techniques.reduction.InterfaceReduction;
import br.edu.ufcg.splab.techniques.reduction.requirement.InterfaceRequirement;
import br.edu.ufcg.splab.techniques.reduction.requirement.RequirementTracer;

public class Harrold implements InterfaceReduction{
	
	/**
	 * To apply this heuristic it is necessary to calculate the cardinality of each
	 * test requirement. The cardinality is the number of test cases that meet that
	 * test requirement. Initially, the algorithm identifies the most essential test
	 * cases, i.e., the test requirement(s) with the lowest cardinality. Each
	 * essential test case is added to the reduced set and all requirements covered by
	 * that test case are marked.
	 * 
	 * Next, the test case that occurs most frequently is selected among
	 * test requirements with the lowest cardinality. If there is a tie, the algorithm
	 * chooses the test case that occurs most frequently at the next highest
	 * requirement cardinality and so on (if there is a tie and the requirement cardinality is
	 * maximum, then the random choice is applied).
	 * 
	 * @param suite The complete test suite.
	 * @param tracers The ordered list of requirements of the complete test suite.
	 * @return The reduced test suite.
	 */
	public TestSuite reduceTestSuite(TestSuite suite, List<RequirementTracer> tracers) {
		List<TestCase> verifiedTests = new ArrayList<TestCase>(); 
		List<TestCase> reducedTests = new ArrayList<TestCase>();
		List<TestCase> toVerify = new ArrayList<TestCase>(suite.getTestSuite());

		for(RequirementTracer tracer: tracers){		
			reducedTests.addAll(verifiedTests);
			
			toVerify = new ArrayList<TestCase>(suite.getTestSuite());
			toVerify.removeAll(reducedTests);
			
			
			verifiedTests = new ArrayList<TestCase>();		
			
			List<InterfaceRequirement> requirements = tracer.getRequirements();
			for(TestCase tc: reducedTests){
				requirements.removeAll(tracer.getRequirementsSatisfied(tc));
			}
			List<InterfaceRequirement> notCovered = new ArrayList<InterfaceRequirement>(requirements);
			

			Map<Integer, List<InterfaceRequirement>> cardinalityMap = this.getCardinalityMap(tracer);
			
			int currentCardinality = 1;
			do{
				TestCase toGet = this.getMostFrequentlyOccurring(cardinalityMap , currentCardinality, toVerify, notCovered, tracer);
				currentCardinality = currentCardinality < maxCardinality(cardinalityMap )	
				 ? currentCardinality + 1 : 1;
				
				if(toGet == null){
					continue;
				}
				List<InterfaceRequirement> covered = tracer.getRequirementsSatisfied(toGet);
				notCovered.removeAll(covered);
				toVerify.remove(toGet);
				verifiedTests.add(toGet);
			}while(notCovered.size() > 0);
			
		}
		reducedTests.addAll(verifiedTests);		
		
		TestSuite newTS = new TestSuite();
		newTS.addAll(reducedTests);
		return newTS;
	}
	
	
	private TestCase getMostFrequentlyOccurring(Map<Integer, List<InterfaceRequirement>> cardinalityMap , int currentCardinality, List<TestCase> toVerify, List<InterfaceRequirement> notCovered, RequirementTracer tracer) {
		List<InterfaceRequirement> reqsWithCard = this.getRequirementsWithCardinality(tracer, currentCardinality, notCovered);
		List<TestCase> mostFrequent = this.getMostFrequentlyOccurring(reqsWithCard, toVerify, tracer);
		if(reqsWithCard.size() == 0 || mostFrequent.size() == 0){
			return null;
		}
		int maxCardinality = this.maxCardinality(cardinalityMap );
		if(mostFrequent.size() > 1 && currentCardinality < maxCardinality){
			int card = currentCardinality + 1;
			reqsWithCard = this.getRequirementsWithCardinality(tracer, card, notCovered);
			TestCase tc = this.getMostFrequentlyOccurringTie(cardinalityMap , mostFrequent, toVerify, reqsWithCard, notCovered, tracer, card);
			return tc;
		}else if (currentCardinality == maxCardinality){
			TestCase testCaseSelected = mostFrequent.get(new Random().nextInt(mostFrequent.size()));			
			return testCaseSelected;
		}else{
			TestCase tc = mostFrequent.get(0);
			return tc;
		}
	}
	
	private List<TestCase> getMostFrequentlyOccurring(List<InterfaceRequirement> reqsWithCard, List<TestCase> toVerify, RequirementTracer tracer) {
		Set<TestCase> testCases = new HashSet<TestCase>();
		for (InterfaceRequirement requirement : reqsWithCard){
			List<TestCase> reqs = tracer.getTestCasesThatSatisfy(requirement);
			testCases.addAll(reqs);
		}
		int maximumTimes = 0;
		List<TestCase> selected = new ArrayList<TestCase>();
		for (TestCase testCase : testCases){
			int times = tracer.getRequirementsSatisfied(testCase, reqsWithCard).size(); 
			if(times > maximumTimes){
				maximumTimes = times;
				selected.clear();
			}
			if(times == maximumTimes){
				selected.add(testCase);
			}
		}
		return selected;
	}
	
	
	private TestCase getMostFrequentlyOccurringTie(Map<Integer, List<InterfaceRequirement>> cardinalityMap , List<TestCase> mostFrequent, List<TestCase> toVerify, List<InterfaceRequirement> reqsWithCard, List<InterfaceRequirement> notCovered, RequirementTracer tracer, int currentCardinality) {
		int maximumTimes = 0;
		for (TestCase testCase : mostFrequent) {
			int times = tracer.getRequirementsSatisfied(testCase, reqsWithCard).size();
			if(times > maximumTimes) {
				maximumTimes = times;
			}
		}
		List<TestCase> auxiliar = new ArrayList<TestCase>();
		for (TestCase testCase : mostFrequent) {
			if(tracer.getRequirementsSatisfied(testCase, reqsWithCard).size() == maximumTimes) {
				auxiliar.add(testCase);
			}
		}
		
		if(auxiliar.size() == 1){
			return auxiliar.get(0);
		}else{
			int maxCardinality = maxCardinality(cardinalityMap );
			if(currentCardinality < maxCardinality){
				int cardinality = currentCardinality + 1;
				List<InterfaceRequirement> reqs = getRequirementsWithCardinality(tracer, cardinality, notCovered);
				return getMostFrequentlyOccurringTie(cardinalityMap , auxiliar, toVerify, reqs, notCovered, tracer, cardinality);
			}else{
				TestCase testCasesSelected = auxiliar.get(new Random().nextInt(auxiliar.size()));
				return testCasesSelected;
			}
		}
	}
	
	private Map<Integer, List<InterfaceRequirement>> getCardinalityMap(RequirementTracer tracer) {
		Map<Integer, List<InterfaceRequirement>> cardinalityMap = new HashMap<Integer, List<InterfaceRequirement>>();
		for (InterfaceRequirement requirement : tracer.getRequirements()) {
			Integer cardinality = tracer.cardinality(requirement);
			List<InterfaceRequirement> fromMap = cardinalityMap.get(cardinality);
			if(fromMap == null)	{
				fromMap = new ArrayList<InterfaceRequirement>();
			}
			fromMap.add(requirement);
			cardinalityMap.put(cardinality, fromMap);
		}
		return cardinalityMap;
	}

	private List<InterfaceRequirement> getRequirementsWithCardinality(RequirementTracer tracer, int cardinality, List<InterfaceRequirement> notCovered){
		List<InterfaceRequirement> result = new ArrayList<InterfaceRequirement>();
		for (InterfaceRequirement requirement : notCovered) {
			if(tracer.cardinality(requirement) == cardinality){
				result.add(requirement);
			}
		}
		return result;
	}
	
	private int maxCardinality(Map<Integer, List<InterfaceRequirement>> cardinalityMap ) {
		int max = 0;
		for (Entry<Integer, List<InterfaceRequirement>> card : cardinalityMap.entrySet()) {
			Integer cardinality = card.getKey();
			if(cardinality > max){
				max = cardinality;
			}
		}
		return max;
	}
	
	/**
     * Provide a string representation of the technique to be written.
     */
	public String toString() {
		return "HGS";
	}
	
}
